1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package com.sun.imageio.plugins.png;
27
28 import java.awt.Rectangle;
29 import java.awt.image.ColorModel;
30 import java.awt.image.IndexColorModel;
31 import java.awt.image.Raster;
32 import java.awt.image.WritableRaster;
33 import java.awt.image.RenderedImage;
34 import java.awt.image.SampleModel;
35 import java.io.ByteArrayOutputStream;
36 import java.io.DataOutput;
37 import java.io.IOException;
38 import java.io.OutputStream;
39 import java.util.Iterator;
40 import java.util.Locale;
41 import java.util.zip.Deflater;
42 import java.util.zip.DeflaterOutputStream;
43 import javax.imageio.IIOException;
44 import javax.imageio.IIOImage;
45 import javax.imageio.ImageTypeSpecifier;
46 import javax.imageio.ImageWriteParam;
47 import javax.imageio.ImageWriter;
48 import javax.imageio.metadata.IIOMetadata;
49 import javax.imageio.metadata.IIOMetadata;
50 import javax.imageio.spi.ImageWriterSpi;
51 import javax.imageio.stream.ImageOutputStream;
52 import javax.imageio.stream.ImageOutputStreamImpl;
53
54 class CRC {
55
56 private static int[] crcTable = new int[256];
57 private int crc = 0xffffffff;
58
59 static {
60
61 for (int n = 0; n < 256; n++) {
62 int c = n;
63 for (int k = 0; k < 8; k++) {
64 if ((c & 1) == 1) {
65 c = 0xedb88320 ^ (c >>> 1);
66 } else {
67 c >>>= 1;
68 }
69
70 crcTable[n] = c;
71 }
72 }
73 }
74
75 public CRC() {}
76
77 public void reset() {
78 crc = 0xffffffff;
79 }
80
81 public void update(byte[] data, int off, int len) {
82 for (int n = 0; n < len; n++) {
83 crc = crcTable[(crc ^ data[off + n]) & 0xff] ^ (crc >>> 8);
84 }
85 }
86
87 public void update(int data) {
88 crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8);
89 }
90
91 public int getValue() {
92 return crc ^ 0xffffffff;
93 }
94 }
95
96
97 final class ChunkStream extends ImageOutputStreamImpl {
98
99 private ImageOutputStream stream;
100 private long startPos;
101 private CRC crc = new CRC();
102
103 public ChunkStream(int type, ImageOutputStream stream) throws IOException {
104 this.stream = stream;
105 this.startPos = stream.getStreamPosition();
106
107 stream.writeInt(-1);
108 writeInt(type);
109 }
110
111 public int read() throws IOException {
112 throw new RuntimeException("Method not available");
113 }
114
115 public int read(byte[] b, int off, int len) throws IOException {
116 throw new RuntimeException("Method not available");
117 }
118
119 public void write(byte[] b, int off, int len) throws IOException {
120 crc.update(b, off, len);
121 stream.write(b, off, len);
122 }
123
124 public void write(int b) throws IOException {
125 crc.update(b);
126 stream.write(b);
127 }
128
129 public void finish() throws IOException {
130
131 stream.writeInt(crc.getValue());
132
133
134 long pos = stream.getStreamPosition();
135 stream.seek(startPos);
136 stream.writeInt((int)(pos - startPos) - 12);
137
138
139 stream.seek(pos);
140 stream.flushBefore(pos);
141 }
142
143 protected void finalize() throws Throwable {
144
145
146 }
147 }
148
149
150
151 final class IDATOutputStream extends ImageOutputStreamImpl {
152
153 private static byte[] chunkType = {
154 (byte)'I', (byte)'D', (byte)'A', (byte)'T'
155 };
156
157 private ImageOutputStream stream;
158 private int chunkLength;
159 private long startPos;
160 private CRC crc = new CRC();
161
162 Deflater def = new Deflater(Deflater.BEST_COMPRESSION);
163 byte[] buf = new byte[512];
164
165 private int bytesRemaining;
166
167 public IDATOutputStream(ImageOutputStream stream, int chunkLength)
168 throws IOException {
169 this.stream = stream;
170 this.chunkLength = chunkLength;
171 startChunk();
172 }
173
174 private void startChunk() throws IOException {
175 crc.reset();
176 this.startPos = stream.getStreamPosition();
177 stream.writeInt(-1);
178
179 crc.update(chunkType, 0, 4);
180 stream.write(chunkType, 0, 4);
181
182 this.bytesRemaining = chunkLength;
183 }
184
185 private void finishChunk() throws IOException {
186
187 stream.writeInt(crc.getValue());
188
189
190 long pos = stream.getStreamPosition();
191 stream.seek(startPos);
192 stream.writeInt((int)(pos - startPos) - 12);
193
194
195 stream.seek(pos);
196 stream.flushBefore(pos);
197 }
198
199 public int read() throws IOException {
200 throw new RuntimeException("Method not available");
201 }
202
203 public int read(byte[] b, int off, int len) throws IOException {
204 throw new RuntimeException("Method not available");
205 }
206
207 public void write(byte[] b, int off, int len) throws IOException {
208 if (len == 0) {
209 return;
210 }
211
212 if (!def.finished()) {
213 def.setInput(b, off, len);
214 while (!def.needsInput()) {
215 deflate();
216 }
217 }
218 }
219
220 public void deflate() throws IOException {
221 int len = def.deflate(buf, 0, buf.length);
222 int off = 0;
223
224 while (len > 0) {
225 if (bytesRemaining == 0) {
226 finishChunk();
227 startChunk();
228 }
229
230 int nbytes = Math.min(len, bytesRemaining);
231 crc.update(buf, off, nbytes);
232 stream.write(buf, off, nbytes);
233
234 off += nbytes;
235 len -= nbytes;
236 bytesRemaining -= nbytes;
237 }
238 }
239
240 public void write(int b) throws IOException {
241 byte[] wbuf = new byte[1];
242 wbuf[0] = (byte)b;
243 write(wbuf, 0, 1);
244 }
245
246 public void finish() throws IOException {
247 try {
248 if (!def.finished()) {
249 def.finish();
250 while (!def.finished()) {
251 deflate();
252 }
253 }
254 finishChunk();
255 } finally {
256 def.end();
257 }
258 }
259
260 protected void finalize() throws Throwable {
261
262
263 }
264 }
265
266
267 class PNGImageWriteParam extends ImageWriteParam {
268
269 public PNGImageWriteParam(Locale locale) {
270 super();
271 this.canWriteProgressive = true;
272 this.locale = locale;
273 }
274 }
275
276
277
278 public class PNGImageWriter extends ImageWriter {
279
280 ImageOutputStream stream = null;
281
282 PNGMetadata metadata = null;
283
284
285 int sourceXOffset = 0;
286 int sourceYOffset = 0;
287 int sourceWidth = 0;
288 int sourceHeight = 0;
289 int[] sourceBands = null;
290 int periodX = 1;
291 int periodY = 1;
292
293 int numBands;
294 int bpp;
295
296 RowFilter rowFilter = new RowFilter();
297 byte[] prevRow = null;
298 byte[] currRow = null;
299 byte[][] filteredRows = null;
300
301
302
303
304
305
306
307
308
309 int[] sampleSize = null;
310 int scalingBitDepth = -1;
311
312
313 byte[][] scale = null;
314 byte[] scale0 = null;
315
316
317 byte[][] scaleh = null;
318 byte[][] scalel = null;
319
320 int totalPixels;
321 int pixelsDone;
322
323 public PNGImageWriter(ImageWriterSpi originatingProvider) {
324 super(originatingProvider);
325 }
326
327 public void setOutput(Object output) {
328 super.setOutput(output);
329 if (output != null) {
330 if (!(output instanceof ImageOutputStream)) {
331 throw new IllegalArgumentException("output not an ImageOutputStream!");
332 }
333 this.stream = (ImageOutputStream)output;
334 } else {
335 this.stream = null;
336 }
337 }
338
339 private static int[] allowedProgressivePasses = { 1, 7 };
340
341 public ImageWriteParam getDefaultWriteParam() {
342 return new PNGImageWriteParam(getLocale());
343 }
344
345 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
346 return null;
347 }
348
349 public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
350 ImageWriteParam param) {
351 PNGMetadata m = new PNGMetadata();
352 m.initialize(imageType, imageType.getSampleModel().getNumBands());
353 return m;
354 }
355
356 public IIOMetadata convertStreamMetadata(IIOMetadata inData,
357 ImageWriteParam param) {
358 return null;
359 }
360
361 public IIOMetadata convertImageMetadata(IIOMetadata inData,
362 ImageTypeSpecifier imageType,
363 ImageWriteParam param) {
364
365 if (inData instanceof PNGMetadata) {
366 return (PNGMetadata)((PNGMetadata)inData).clone();
367 } else {
368 return new PNGMetadata(inData);
369 }
370 }
371
372 private void write_magic() throws IOException {
373
374 byte[] magic = { (byte)137, 80, 78, 71, 13, 10, 26, 10 };
375 stream.write(magic);
376 }
377
378 private void write_IHDR() throws IOException {
379
380 ChunkStream cs = new ChunkStream(PNGImageReader.IHDR_TYPE, stream);
381 cs.writeInt(metadata.IHDR_width);
382 cs.writeInt(metadata.IHDR_height);
383 cs.writeByte(metadata.IHDR_bitDepth);
384 cs.writeByte(metadata.IHDR_colorType);
385 if (metadata.IHDR_compressionMethod != 0) {
386 throw new IIOException(
387 "Only compression method 0 is defined in PNG 1.1");
388 }
389 cs.writeByte(metadata.IHDR_compressionMethod);
390 if (metadata.IHDR_filterMethod != 0) {
391 throw new IIOException(
392 "Only filter method 0 is defined in PNG 1.1");
393 }
394 cs.writeByte(metadata.IHDR_filterMethod);
395 if (metadata.IHDR_interlaceMethod < 0 ||
396 metadata.IHDR_interlaceMethod > 1) {
397 throw new IIOException(
398 "Only interlace methods 0 (node) and 1 (adam7) are defined in PNG 1.1");
399 }
400 cs.writeByte(metadata.IHDR_interlaceMethod);
401 cs.finish();
402 }
403
404 private void write_cHRM() throws IOException {
405 if (metadata.cHRM_present) {
406 ChunkStream cs = new ChunkStream(PNGImageReader.cHRM_TYPE, stream);
407 cs.writeInt(metadata.cHRM_whitePointX);
408 cs.writeInt(metadata.cHRM_whitePointY);
409 cs.writeInt(metadata.cHRM_redX);
410 cs.writeInt(metadata.cHRM_redY);
411 cs.writeInt(metadata.cHRM_greenX);
412 cs.writeInt(metadata.cHRM_greenY);
413 cs.writeInt(metadata.cHRM_blueX);
414 cs.writeInt(metadata.cHRM_blueY);
415 cs.finish();
416 }
417 }
418
419 private void write_gAMA() throws IOException {
420 if (metadata.gAMA_present) {
421 ChunkStream cs = new ChunkStream(PNGImageReader.gAMA_TYPE, stream);
422 cs.writeInt(metadata.gAMA_gamma);
423 cs.finish();
424 }
425 }
426
427 private void write_iCCP() throws IOException {
428 if (metadata.iCCP_present) {
429 ChunkStream cs = new ChunkStream(PNGImageReader.iCCP_TYPE, stream);
430 cs.writeBytes(metadata.iCCP_profileName);
431 cs.writeByte(0);
432
433 cs.writeByte(metadata.iCCP_compressionMethod);
434 cs.write(metadata.iCCP_compressedProfile);
435 cs.finish();
436 }
437 }
438
439 private void write_sBIT() throws IOException {
440 if (metadata.sBIT_present) {
441 ChunkStream cs = new ChunkStream(PNGImageReader.sBIT_TYPE, stream);
442 int colorType = metadata.IHDR_colorType;
443 if (metadata.sBIT_colorType != colorType) {
444 processWarningOccurred(0,
445 "sBIT metadata has wrong color type.\n" +
446 "The chunk will not be written.");
447 return;
448 }
449
450 if (colorType == PNGImageReader.PNG_COLOR_GRAY ||
451 colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
452 cs.writeByte(metadata.sBIT_grayBits);
453 } else if (colorType == PNGImageReader.PNG_COLOR_RGB ||
454 colorType == PNGImageReader.PNG_COLOR_PALETTE ||
455 colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
456 cs.writeByte(metadata.sBIT_redBits);
457 cs.writeByte(metadata.sBIT_greenBits);
458 cs.writeByte(metadata.sBIT_blueBits);
459 }
460
461 if (colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA ||
462 colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
463 cs.writeByte(metadata.sBIT_alphaBits);
464 }
465 cs.finish();
466 }
467 }
468
469 private void write_sRGB() throws IOException {
470 if (metadata.sRGB_present) {
471 ChunkStream cs = new ChunkStream(PNGImageReader.sRGB_TYPE, stream);
472 cs.writeByte(metadata.sRGB_renderingIntent);
473 cs.finish();
474 }
475 }
476
477 private void write_PLTE() throws IOException {
478 if (metadata.PLTE_present) {
479 if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY ||
480 metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
481
482
483 processWarningOccurred(0,
484 "A PLTE chunk may not appear in a gray or gray alpha image.\n" +
485 "The chunk will not be written");
486 return;
487 }
488
489 ChunkStream cs = new ChunkStream(PNGImageReader.PLTE_TYPE, stream);
490
491 int numEntries = metadata.PLTE_red.length;
492 byte[] palette = new byte[numEntries*3];
493 int index = 0;
494 for (int i = 0; i < numEntries; i++) {
495 palette[index++] = metadata.PLTE_red[i];
496 palette[index++] = metadata.PLTE_green[i];
497 palette[index++] = metadata.PLTE_blue[i];
498 }
499
500 cs.write(palette);
501 cs.finish();
502 }
503 }
504
505 private void write_hIST() throws IOException, IIOException {
506 if (metadata.hIST_present) {
507 ChunkStream cs = new ChunkStream(PNGImageReader.hIST_TYPE, stream);
508
509 if (!metadata.PLTE_present) {
510 throw new IIOException("hIST chunk without PLTE chunk!");
511 }
512
513 cs.writeChars(metadata.hIST_histogram,
514 0, metadata.hIST_histogram.length);
515 cs.finish();
516 }
517 }
518
519 private void write_tRNS() throws IOException, IIOException {
520 if (metadata.tRNS_present) {
521 ChunkStream cs = new ChunkStream(PNGImageReader.tRNS_TYPE, stream);
522 int colorType = metadata.IHDR_colorType;
523 int chunkType = metadata.tRNS_colorType;
524
525
526
527 int chunkRed = metadata.tRNS_red;
528 int chunkGreen = metadata.tRNS_green;
529 int chunkBlue = metadata.tRNS_blue;
530 if (colorType == PNGImageReader.PNG_COLOR_RGB &&
531 chunkType == PNGImageReader.PNG_COLOR_GRAY) {
532 chunkType = colorType;
533 chunkRed = chunkGreen = chunkBlue =
534 metadata.tRNS_gray;
535 }
536
537 if (chunkType != colorType) {
538 processWarningOccurred(0,
539 "tRNS metadata has incompatible color type.\n" +
540 "The chunk will not be written.");
541 return;
542 }
543
544 if (colorType == PNGImageReader.PNG_COLOR_PALETTE) {
545 if (!metadata.PLTE_present) {
546 throw new IIOException("tRNS chunk without PLTE chunk!");
547 }
548 cs.write(metadata.tRNS_alpha);
549 } else if (colorType == PNGImageReader.PNG_COLOR_GRAY) {
550 cs.writeShort(metadata.tRNS_gray);
551 } else if (colorType == PNGImageReader.PNG_COLOR_RGB) {
552 cs.writeShort(chunkRed);
553 cs.writeShort(chunkGreen);
554 cs.writeShort(chunkBlue);
555 } else {
556 throw new IIOException("tRNS chunk for color type 4 or 6!");
557 }
558 cs.finish();
559 }
560 }
561
562 private void write_bKGD() throws IOException {
563 if (metadata.bKGD_present) {
564 ChunkStream cs = new ChunkStream(PNGImageReader.bKGD_TYPE, stream);
565 int colorType = metadata.IHDR_colorType & 0x3;
566 int chunkType = metadata.bKGD_colorType;
567
568
569
570 int chunkRed = metadata.bKGD_red;
571 int chunkGreen = metadata.bKGD_red;
572 int chunkBlue = metadata.bKGD_red;
573 if (colorType == PNGImageReader.PNG_COLOR_RGB &&
574 chunkType == PNGImageReader.PNG_COLOR_GRAY) {
575
576 chunkType = colorType;
577 chunkRed = chunkGreen = chunkBlue =
578 metadata.bKGD_gray;
579 }
580
581
582 if (chunkType != colorType) {
583 processWarningOccurred(0,
584 "bKGD metadata has incompatible color type.\n" +
585 "The chunk will not be written.");
586 return;
587 }
588
589 if (colorType == PNGImageReader.PNG_COLOR_PALETTE) {
590 cs.writeByte(metadata.bKGD_index);
591 } else if (colorType == PNGImageReader.PNG_COLOR_GRAY ||
592 colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
593 cs.writeShort(metadata.bKGD_gray);
594 } else {
595
596 cs.writeShort(chunkRed);
597 cs.writeShort(chunkGreen);
598 cs.writeShort(chunkBlue);
599 }
600 cs.finish();
601 }
602 }
603
604 private void write_pHYs() throws IOException {
605 if (metadata.pHYs_present) {
606 ChunkStream cs = new ChunkStream(PNGImageReader.pHYs_TYPE, stream);
607 cs.writeInt(metadata.pHYs_pixelsPerUnitXAxis);
608 cs.writeInt(metadata.pHYs_pixelsPerUnitYAxis);
609 cs.writeByte(metadata.pHYs_unitSpecifier);
610 cs.finish();
611 }
612 }
613
614 private void write_sPLT() throws IOException {
615 if (metadata.sPLT_present) {
616 ChunkStream cs = new ChunkStream(PNGImageReader.sPLT_TYPE, stream);
617
618 cs.writeBytes(metadata.sPLT_paletteName);
619 cs.writeByte(0);
620
621 cs.writeByte(metadata.sPLT_sampleDepth);
622 int numEntries = metadata.sPLT_red.length;
623
624 if (metadata.sPLT_sampleDepth == 8) {
625 for (int i = 0; i < numEntries; i++) {
626 cs.writeByte(metadata.sPLT_red[i]);
627 cs.writeByte(metadata.sPLT_green[i]);
628 cs.writeByte(metadata.sPLT_blue[i]);
629 cs.writeByte(metadata.sPLT_alpha[i]);
630 cs.writeShort(metadata.sPLT_frequency[i]);
631 }
632 } else {
633 for (int i = 0; i < numEntries; i++) {
634 cs.writeShort(metadata.sPLT_red[i]);
635 cs.writeShort(metadata.sPLT_green[i]);
636 cs.writeShort(metadata.sPLT_blue[i]);
637 cs.writeShort(metadata.sPLT_alpha[i]);
638 cs.writeShort(metadata.sPLT_frequency[i]);
639 }
640 }
641 cs.finish();
642 }
643 }
644
645 private void write_tIME() throws IOException {
646 if (metadata.tIME_present) {
647 ChunkStream cs = new ChunkStream(PNGImageReader.tIME_TYPE, stream);
648 cs.writeShort(metadata.tIME_year);
649 cs.writeByte(metadata.tIME_month);
650 cs.writeByte(metadata.tIME_day);
651 cs.writeByte(metadata.tIME_hour);
652 cs.writeByte(metadata.tIME_minute);
653 cs.writeByte(metadata.tIME_second);
654 cs.finish();
655 }
656 }
657
658 private void write_tEXt() throws IOException {
659 Iterator keywordIter = metadata.tEXt_keyword.iterator();
660 Iterator textIter = metadata.tEXt_text.iterator();
661
662 while (keywordIter.hasNext()) {
663 ChunkStream cs = new ChunkStream(PNGImageReader.tEXt_TYPE, stream);
664 String keyword = (String)keywordIter.next();
665 cs.writeBytes(keyword);
666 cs.writeByte(0);
667
668 String text = (String)textIter.next();
669 cs.writeBytes(text);
670 cs.finish();
671 }
672 }
673
674 private byte[] deflate(byte[] b) throws IOException {
675 ByteArrayOutputStream baos = new ByteArrayOutputStream();
676 DeflaterOutputStream dos = new DeflaterOutputStream(baos);
677 dos.write(b);
678 dos.close();
679 return baos.toByteArray();
680 }
681
682 private void write_iTXt() throws IOException {
683 Iterator<String> keywordIter = metadata.iTXt_keyword.iterator();
684 Iterator<Boolean> flagIter = metadata.iTXt_compressionFlag.iterator();
685 Iterator<Integer> methodIter = metadata.iTXt_compressionMethod.iterator();
686 Iterator<String> languageIter = metadata.iTXt_languageTag.iterator();
687 Iterator<String> translatedKeywordIter =
688 metadata.iTXt_translatedKeyword.iterator();
689 Iterator<String> textIter = metadata.iTXt_text.iterator();
690
691 while (keywordIter.hasNext()) {
692 ChunkStream cs = new ChunkStream(PNGImageReader.iTXt_TYPE, stream);
693
694 cs.writeBytes(keywordIter.next());
695 cs.writeByte(0);
696
697 Boolean compressed = flagIter.next();
698 cs.writeByte(compressed ? 1 : 0);
699
700 cs.writeByte(methodIter.next().intValue());
701
702 cs.writeBytes(languageIter.next());
703 cs.writeByte(0);
704
705
706 cs.write(translatedKeywordIter.next().getBytes("UTF8"));
707 cs.writeByte(0);
708
709 String text = textIter.next();
710 if (compressed) {
711 cs.write(deflate(text.getBytes("UTF8")));
712 } else {
713 cs.write(text.getBytes("UTF8"));
714 }
715 cs.finish();
716 }
717 }
718
719 private void write_zTXt() throws IOException {
720 Iterator keywordIter = metadata.zTXt_keyword.iterator();
721 Iterator methodIter = metadata.zTXt_compressionMethod.iterator();
722 Iterator textIter = metadata.zTXt_text.iterator();
723
724 while (keywordIter.hasNext()) {
725 ChunkStream cs = new ChunkStream(PNGImageReader.zTXt_TYPE, stream);
726 String keyword = (String)keywordIter.next();
727 cs.writeBytes(keyword);
728 cs.writeByte(0);
729
730 int compressionMethod = ((Integer)methodIter.next()).intValue();
731 cs.writeByte(compressionMethod);
732
733 String text = (String)textIter.next();
734 cs.write(deflate(text.getBytes("ISO-8859-1")));
735 cs.finish();
736 }
737 }
738
739 private void writeUnknownChunks() throws IOException {
740 Iterator typeIter = metadata.unknownChunkType.iterator();
741 Iterator dataIter = metadata.unknownChunkData.iterator();
742
743 while (typeIter.hasNext() && dataIter.hasNext()) {
744 String type = (String)typeIter.next();
745 ChunkStream cs = new ChunkStream(chunkType(type), stream);
746 byte[] data = (byte[])dataIter.next();
747 cs.write(data);
748 cs.finish();
749 }
750 }
751
752 private static int chunkType(String typeString) {
753 char c0 = typeString.charAt(0);
754 char c1 = typeString.charAt(1);
755 char c2 = typeString.charAt(2);
756 char c3 = typeString.charAt(3);
757
758 int type = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3;
759 return type;
760 }
761
762 private void encodePass(ImageOutputStream os,
763 RenderedImage image,
764 int xOffset, int yOffset,
765 int xSkip, int ySkip) throws IOException {
766 int minX = sourceXOffset;
767 int minY = sourceYOffset;
768 int width = sourceWidth;
769 int height = sourceHeight;
770
771
772 xOffset *= periodX;
773 xSkip *= periodX;
774 yOffset *= periodY;
775 ySkip *= periodY;
776
777
778 int hpixels = (width - xOffset + xSkip - 1)/xSkip;
779 int vpixels = (height - yOffset + ySkip - 1)/ySkip;
780 if (hpixels == 0 || vpixels == 0) {
781 return;
782 }
783
784
785 xOffset *= numBands;
786 xSkip *= numBands;
787
788
789 int samplesPerByte = 8/metadata.IHDR_bitDepth;
790 int numSamples = width*numBands;
791 int[] samples = new int[numSamples];
792
793 int bytesPerRow = hpixels*numBands;
794 if (metadata.IHDR_bitDepth < 8) {
795 bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte;
796 } else if (metadata.IHDR_bitDepth == 16) {
797 bytesPerRow *= 2;
798 }
799
800 IndexColorModel icm_gray_alpha = null;
801 if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA &&
802 image.getColorModel() instanceof IndexColorModel)
803 {
804
805 bytesPerRow *= 2;
806
807
808 icm_gray_alpha = (IndexColorModel)image.getColorModel();
809 }
810
811 currRow = new byte[bytesPerRow + bpp];
812 prevRow = new byte[bytesPerRow + bpp];
813 filteredRows = new byte[5][bytesPerRow + bpp];
814
815 int bitDepth = metadata.IHDR_bitDepth;
816 for (int row = minY + yOffset; row < minY + height; row += ySkip) {
817 Rectangle rect = new Rectangle(minX, row, width, 1);
818 Raster ras = image.getData(rect);
819 if (sourceBands != null) {
820 ras = ras.createChild(minX, row, width, 1, minX, row,
821 sourceBands);
822 }
823
824 ras.getPixels(minX, row, width, 1, samples);
825
826 if (image.getColorModel().isAlphaPremultiplied()) {
827 WritableRaster wr = ras.createCompatibleWritableRaster();
828 wr.setPixels(wr.getMinX(), wr.getMinY(),
829 wr.getWidth(), wr.getHeight(),
830 samples);
831
832 image.getColorModel().coerceData(wr, false);
833 wr.getPixels(wr.getMinX(), wr.getMinY(),
834 wr.getWidth(), wr.getHeight(),
835 samples);
836 }
837
838
839 int[] paletteOrder = metadata.PLTE_order;
840 if (paletteOrder != null) {
841 for (int i = 0; i < numSamples; i++) {
842 samples[i] = paletteOrder[samples[i]];
843 }
844 }
845
846 int count = bpp;
847 int pos = 0;
848 int tmp = 0;
849
850 switch (bitDepth) {
851 case 1: case 2: case 4:
852
853
854 int mask = samplesPerByte - 1;
855 for (int s = xOffset; s < numSamples; s += xSkip) {
856 byte val = scale0[samples[s]];
857 tmp = (tmp << bitDepth) | val;
858
859 if ((pos++ & mask) == mask) {
860 currRow[count++] = (byte)tmp;
861 tmp = 0;
862 pos = 0;
863 }
864 }
865
866
867 if ((pos & mask) != 0) {
868 tmp <<= ((8/bitDepth) - pos)*bitDepth;
869 currRow[count++] = (byte)tmp;
870 }
871 break;
872
873 case 8:
874 if (numBands == 1) {
875 for (int s = xOffset; s < numSamples; s += xSkip) {
876 currRow[count++] = scale0[samples[s]];
877 if (icm_gray_alpha != null) {
878 currRow[count++] =
879 scale0[icm_gray_alpha.getAlpha(0xff & samples[s])];
880 }
881 }
882 } else {
883 for (int s = xOffset; s < numSamples; s += xSkip) {
884 for (int b = 0; b < numBands; b++) {
885 currRow[count++] = scale[b][samples[s + b]];
886 }
887 }
888 }
889 break;
890
891 case 16:
892 for (int s = xOffset; s < numSamples; s += xSkip) {
893 for (int b = 0; b < numBands; b++) {
894 currRow[count++] = scaleh[b][samples[s + b]];
895 currRow[count++] = scalel[b][samples[s + b]];
896 }
897 }
898 break;
899 }
900
901
902 int filterType = rowFilter.filterRow(metadata.IHDR_colorType,
903 currRow, prevRow,
904 filteredRows,
905 bytesPerRow, bpp);
906
907 os.write(filterType);
908 os.write(filteredRows[filterType], bpp, bytesPerRow);
909
910
911 byte[] swap = currRow;
912 currRow = prevRow;
913 prevRow = swap;
914
915 pixelsDone += hpixels;
916 processImageProgress(100.0F*pixelsDone/totalPixels);
917
918
919
920 if (abortRequested()) {
921 return;
922 }
923 }
924 }
925
926
927 private void write_IDAT(RenderedImage image) throws IOException {
928 IDATOutputStream ios = new IDATOutputStream(stream, 32768);
929 try {
930 if (metadata.IHDR_interlaceMethod == 1) {
931 for (int i = 0; i < 7; i++) {
932 encodePass(ios, image,
933 PNGImageReader.adam7XOffset[i],
934 PNGImageReader.adam7YOffset[i],
935 PNGImageReader.adam7XSubsampling[i],
936 PNGImageReader.adam7YSubsampling[i]);
937 if (abortRequested()) {
938 break;
939 }
940 }
941 } else {
942 encodePass(ios, image, 0, 0, 1, 1);
943 }
944 } finally {
945 ios.finish();
946 }
947 }
948
949 private void writeIEND() throws IOException {
950 ChunkStream cs = new ChunkStream(PNGImageReader.IEND_TYPE, stream);
951 cs.finish();
952 }
953
954
955
956 private boolean equals(int[] s0, int[] s1) {
957 if (s0 == null || s1 == null) {
958 return false;
959 }
960 if (s0.length != s1.length) {
961 return false;
962 }
963 for (int i = 0; i < s0.length; i++) {
964 if (s0[i] != s1[i]) {
965 return false;
966 }
967 }
968 return true;
969 }
970
971
972
973
974 private void initializeScaleTables(int[] sampleSize) {
975 int bitDepth = metadata.IHDR_bitDepth;
976
977
978 if (bitDepth == scalingBitDepth &&
979 equals(sampleSize, this.sampleSize)) {
980 return;
981 }
982
983
984 this.sampleSize = sampleSize;
985 this.scalingBitDepth = bitDepth;
986 int maxOutSample = (1 << bitDepth) - 1;
987 if (bitDepth <= 8) {
988 scale = new byte[numBands][];
989 for (int b = 0; b < numBands; b++) {
990 int maxInSample = (1 << sampleSize[b]) - 1;
991 int halfMaxInSample = maxInSample/2;
992 scale[b] = new byte[maxInSample + 1];
993 for (int s = 0; s <= maxInSample; s++) {
994 scale[b][s] =
995 (byte)((s*maxOutSample + halfMaxInSample)/maxInSample);
996 }
997 }
998 scale0 = scale[0];
999 scaleh = scalel = null;
1000 } else {
1001
1002 scaleh = new byte[numBands][];
1003 scalel = new byte[numBands][];
1004
1005 for (int b = 0; b < numBands; b++) {
1006 int maxInSample = (1 << sampleSize[b]) - 1;
1007 int halfMaxInSample = maxInSample/2;
1008 scaleh[b] = new byte[maxInSample + 1];
1009 scalel[b] = new byte[maxInSample + 1];
1010 for (int s = 0; s <= maxInSample; s++) {
1011 int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
1012 scaleh[b][s] = (byte)(val >> 8);
1013 scalel[b][s] = (byte)(val & 0xff);
1014 }
1015 }
1016 scale = null;
1017 scale0 = null;
1018 }
1019 }
1020
1021 public void write(IIOMetadata streamMetadata,
1022 IIOImage image,
1023 ImageWriteParam param) throws IIOException {
1024 if (stream == null) {
1025 throw new IllegalStateException("output == null!");
1026 }
1027 if (image == null) {
1028 throw new IllegalArgumentException("image == null!");
1029 }
1030 if (image.hasRaster()) {
1031 throw new UnsupportedOperationException("image has a Raster!");
1032 }
1033
1034 RenderedImage im = image.getRenderedImage();
1035 SampleModel sampleModel = im.getSampleModel();
1036 this.numBands = sampleModel.getNumBands();
1037
1038
1039 this.sourceXOffset = im.getMinX();
1040 this.sourceYOffset = im.getMinY();
1041 this.sourceWidth = im.getWidth();
1042 this.sourceHeight = im.getHeight();
1043 this.sourceBands = null;
1044 this.periodX = 1;
1045 this.periodY = 1;
1046
1047 if (param != null) {
1048
1049 Rectangle sourceRegion = param.getSourceRegion();
1050 if (sourceRegion != null) {
1051 Rectangle imageBounds = new Rectangle(im.getMinX(),
1052 im.getMinY(),
1053 im.getWidth(),
1054 im.getHeight());
1055
1056 sourceRegion = sourceRegion.intersection(imageBounds);
1057 sourceXOffset = sourceRegion.x;
1058 sourceYOffset = sourceRegion.y;
1059 sourceWidth = sourceRegion.width;
1060 sourceHeight = sourceRegion.height;
1061 }
1062
1063
1064 int gridX = param.getSubsamplingXOffset();
1065 int gridY = param.getSubsamplingYOffset();
1066 sourceXOffset += gridX;
1067 sourceYOffset += gridY;
1068 sourceWidth -= gridX;
1069 sourceHeight -= gridY;
1070
1071
1072 periodX = param.getSourceXSubsampling();
1073 periodY = param.getSourceYSubsampling();
1074
1075 int[] sBands = param.getSourceBands();
1076 if (sBands != null) {
1077 sourceBands = sBands;
1078 numBands = sourceBands.length;
1079 }
1080 }
1081
1082
1083 int destWidth = (sourceWidth + periodX - 1)/periodX;
1084 int destHeight = (sourceHeight + periodY - 1)/periodY;
1085 if (destWidth <= 0 || destHeight <= 0) {
1086 throw new IllegalArgumentException("Empty source region!");
1087 }
1088
1089
1090 this.totalPixels = destWidth*destHeight;
1091 this.pixelsDone = 0;
1092
1093
1094 IIOMetadata imd = image.getMetadata();
1095 if (imd != null) {
1096 metadata = (PNGMetadata)convertImageMetadata(imd,
1097 ImageTypeSpecifier.createFromRenderedImage(im),
1098 null);
1099 } else {
1100 metadata = new PNGMetadata();
1101 }
1102
1103 if (param != null) {
1104
1105 switch (param.getProgressiveMode()) {
1106 case ImageWriteParam.MODE_DEFAULT:
1107 metadata.IHDR_interlaceMethod = 1;
1108 break;
1109 case ImageWriteParam.MODE_DISABLED:
1110 metadata.IHDR_interlaceMethod = 0;
1111 break;
1112
1113
1114 }
1115 }
1116
1117
1118 metadata.initialize(new ImageTypeSpecifier(im), numBands);
1119
1120
1121 metadata.IHDR_width = destWidth;
1122 metadata.IHDR_height = destHeight;
1123
1124 this.bpp = numBands*((metadata.IHDR_bitDepth == 16) ? 2 : 1);
1125
1126
1127 initializeScaleTables(sampleModel.getSampleSize());
1128
1129 clearAbortRequest();
1130
1131 processImageStarted(0);
1132
1133 try {
1134 write_magic();
1135 write_IHDR();
1136
1137 write_cHRM();
1138 write_gAMA();
1139 write_iCCP();
1140 write_sBIT();
1141 write_sRGB();
1142
1143 write_PLTE();
1144
1145 write_hIST();
1146 write_tRNS();
1147 write_bKGD();
1148
1149 write_pHYs();
1150 write_sPLT();
1151 write_tIME();
1152 write_tEXt();
1153 write_iTXt();
1154 write_zTXt();
1155
1156 writeUnknownChunks();
1157
1158 write_IDAT(im);
1159
1160 if (abortRequested()) {
1161 processWriteAborted();
1162 } else {
1163
1164 writeIEND();
1165 processImageComplete();
1166 }
1167 } catch (IOException e) {
1168 throw new IIOException("I/O error writing PNG file!", e);
1169 }
1170 }
1171 }